import MovieFrame from "./MovieFrame";
import {clamp} from "../../utils/number/clamp";

export default class MovieBehavior extends PIXI.EventEmitter {

    constructor(target, onFrameChanged, frameRate = 24) {
        super();
        if (frameRate <= 0) {
            throw new Error("Invalid frame rate");
        }
        if (target == null) {
            throw new Error("Target cannot be null");
        }
        if (onFrameChanged == null) {
            throw new Error("Callback cannot be null");
        }
        this._target = target;
        this._onFrameChanged = onFrameChanged;
        this._frameDuration = 1.0 / frameRate;
        this._frames = [];
        this._loop = true;
        this._playing = true;
        this._currentTime = 0.0;
        this._currentFrame = 0;
        this._wasStopped = true;
    }

    get numFrames() {
        return this._frames.length;
    }

    set numFrames(value) {
        for (let i = this.numFrames; i < value; ++i) {
            this._frames[i] = new MovieFrame();
        }
        this._frames.length = value;
    }

    get totalTime() {
        return this.numFrames * this._frameDuration;
    }

    get currentTime() {
        return this._currentTime;
    }

    set currentTime(value) {
        value = clamp(value, 0, this.totalTime);

        let prevFrame = this._currentFrame;
        this._currentFrame = value / this._frameDuration;
        this._currentTime = value;

        if (prevFrame != this._currentFrame) {
            this._onFrameChanged(this._currentFrame);
        }
    }

    get frameRate() {
        return 1.0 / this._frameDuration;
    }

    set frameRate(value) {
        if (value <= 0) {
            throw new Error("Invalid frame rate");
        }

        let newFrameDuration = 1.0 / value;
        let acceleration = newFrameDuration / this._frameDuration;
        this._currentTime *= acceleration;
        this._frameDuration = newFrameDuration;
    }

    get loop() {
        return this._loop;
    }

    set loop(value) {
        this._loop = value;
    }

    get currentFrame() {
        return this._currentFrame;
    }

    set currentFrame(value) {
        value = clamp(value, 0, this.numFrames);

        let prevFrame = this._currentFrame;
        this._currentTime = this._frameDuration * value;
        this._currentFrame = value;

        if (prevFrame != this._currentFrame) {
            this._onFrameChanged(this._currentFrame);
        }
    }

    get isPlaying() {
        if (this._playing) {
            return this._loop || this._currentTime < this.totalTime;
        } else {
            return false;
        }
    }

    get isComplete() {
        return !this._loop && this._currentTime >= this.totalTime;
    }

    play() {
        this._playing = true;
    }

    pause() {
        this._playing = false;
    }

    stop() {
        this._playing = false;
        this._wasStopped = true;
        this.currentFrame = 0;
    }

    addFrameAction(index, action) {
        this.getFrameAt(index).addAction(action);
    }

    removeFrameAction(index, action) {
        this.getFrameAt(index).removeAction(action);
    }

    removeFrameActions(index) {
        this.getFrameAt(index).removeActions();
    }

    getFrameAt(index) {
        if (index < 0 || index >= this.numFrames) {
            throw new Error("Invalid frame index");
        }
        return this._frames[index];
    }

    advanceTime(passedTime) {
        if (!this._playing) {
            return;
        }
        var frame = this._frames[this._currentFrame];
        var totalTime = this.totalTime;

        if (this._wasStopped) {
            this._wasStopped = false;
            if (frame.numActions) {
                frame.executeActions(this._target, this._currentFrame);
                this.advanceTime(passedTime);
                return;
            }
        }

        if (this._currentTime >= totalTime) {
            if (this._loop) {
                this._currentTime = 0.0;
                this._currentFrame = 0;
                this._onFrameChanged(0);
                frame = this._frames[0];

                if (frame.numActions) {
                    frame.executeActions(this._target, this._currentFrame);
                    this.advanceTime(passedTime);
                    return;
                }
            }
            else return;
        }

        let finalFrame = this._frames.length - 1;
        let frameStartTime = this._currentFrame * this._frameDuration;
        let dispatchCompleteEvent = false;
        let previousFrame = this._currentFrame;
        let restTimeInFrame;
        let numActions;

        while (this._currentTime + passedTime >= frameStartTime + this._frameDuration) {
            restTimeInFrame = this._frameDuration - this._currentTime + frameStartTime;
            passedTime -= restTimeInFrame;
            this._currentTime = frameStartTime + this._frameDuration;

            if (this._currentFrame == finalFrame) {
                this._currentTime = totalTime; // prevent floating point problem
                if (this.eventNames.indexOf('complete') != -1) {
                    dispatchCompleteEvent = true;
                } else if (this._loop) {
                    this._currentTime = 0;
                    this._currentFrame = 0;
                    frameStartTime = 0;
                }
                else return;
            } else {
                this._currentFrame += 1;
                frameStartTime += this._frameDuration;
            }

            frame = this._frames[this._currentFrame];
            numActions = frame.numActions;

            if (dispatchCompleteEvent) {
                this._onFrameChanged(this._currentFrame);
                this.emit('complete');
                this.advanceTime(passedTime);
                return;
            } else if (numActions) {
                this._onFrameChanged(this._currentFrame);
                frame.executeActions(this._target, this._currentFrame);
                this.advanceTime(passedTime);
                return;
            }
        }

        if (previousFrame != this._currentFrame) {
            this._onFrameChanged(this._currentFrame);
        }
        _currentTime += passedTime;
    }
}
